!pr1
18-Digit Arithmetic, Part 9................Bob Sander-Cederlof

Nearing the home stretch, this month I will cover the DP18 PRINT statement.  I believe that only leaves INPUT for next month.

Normal Applesoft PRINT has a wide variety of options.  PRINT may appear all by itself to print a carriage return, or with one or more expressions.  The expressions may be separated by commas or semicolons:  both are used to separate the expressions for syntax purposes, but commas also cause a form of tabbing.  A final comma or semicolon may be used to suppress the normal carriage return at the end of the printed line.  All numeric values are printed in an unformatted style.

We wanted to have additional formatting capabilities in DP18 PRINT.  Many users of Applesoft have tried to write money handling programs, agonizing over the contortions necessary to make pretty reports.  BASIC on many other micros comes with PRINT USING, which includes a string describing the exact format to use for print a list of items.  Applesoft doesn't have PRINT USING (we have graphics instead, and all in a 10K interpreter).  DP18 does.

DP18 doesn't have everything though.  Here are some things we left out.  Commas may be used to separate items in a DP18 PRINT statement, but no tabbing happens.  Instead, commas cause carriage returns.  DP18 values are so long that comma tabbing seemed useless.  You cannot fit two fully extended unformatted values in one 40-column line.  Maybe you could say we do tab, all the way to the next line.  Anyway, this gives us a useful NEW feature:  the ability for one PRINT statement to print on more than one line.

DP18 PRINT can only print DP18 expressions.  Normal Applesoft real or integer expressions can be printed by normal Applesoft PRINT, or by converting them to DP18 values using VAL and STR$.  Applesoft string expressions can be printed using a DP18 "picture", but not in the simple manner you are used to in normal Applesoft PRINT.

DP18 in its present form supports three different kinds of items in a PRINT statement:  DP18 expressions, #WD items, and $PIC items.

The first kind is the easiest to use, and will remind you a lot of Applesoft.  Since all you tell DP18 is the expression, it makes up its own mind about the format to use.  We call this "unformatted", because it hard to predict how it will look once it is printed.  If the absolute value of the number to be printed is within the range from .01 to 999,999,999,999,999,999 (18 digits) it will print as a normal number, with no leading or trailing blanks and no trailing zeroes.  If outside that range, it will be printed with an E exponent.  Doesn't this remind you of Applesoft?  Here are some examples using numbers (bear in mind they could be long complicated DP18 expressions):
!np
      ]&DP:PRINT 1,2,3;4;5
      1
      2
      345
      ]&DP:PRINT .009,.01,999999999999999999
      9E-3
      .01
      999999999999999999
      ]&DP:PRINT 1000000000000000000
      1E+18
      ]

If a PRINT list item begins with the character "#", it is a #WD formatted item.  Three things follow the "#" character, separated by commas: a field width, the number of fractional digits, and a DP18 expression.  (If you have ever used Fortran, this is going to remind you of the "Fw.d" format.)

      &DP:PRINT #w,d,value

The w and d parameters are Applesoft expressions (or simple constants), and the value is a DP18 expression.  The value will be printed right-justified in a field w-characters wide, with d decimal places after the decimal point.  Leading blanks will be printed if there is room for any.  If the number will not fit in w characters, w asterisks will be printed instead to show you there was an overflow problem.  Values are rounded to the required number of decimal places, not just truncated.  Here are some examples:

      ]&DP:PRINT #8,3,2.04;#8,3,5,#10,5,3.14159,#3,1,99
         2.040   5.000
         3.14159
      ***
      ]&DP:PRINT #8,4,3.14159,#7,3,3.14159,#6,2,3.14159
        3.1416
        3.142
        3.14

      100 FOR I=0TO5
      110 PRINT I;:&DP:PRINT #10-I,5-I,3.1415926
      120 NEXT
      ]RUN
      0   3.14159
      1   3.1416
      2   3.142
      3   3.14
      4   3.1
      5   3.
!np
The third type of PRINT item begins with a dollar sign.  A string constant, variable, or expression follows the dollar sign.  If the picture specifies fields for DP18 or string values to be printed in, then the list of values must follow the picture, all separated by commas.

      &DP:PRINT $ picture
      &DP:PRINT $ picture,list

The "picture" is any Applesoft string expression; it is used as the template for formatting the expressions in the optional list.  The list may have any number of expressions separated by commas as long as they correspond with the picture.  You may even have no expressions at all, which is why I say the list is optional.

The picture consists of a string of characters.  There are four basic types of characters used in pictures:  commands, literals, numbers, and field descriptions.  These are described below.

Any number in the picture makes up a repeat count.  The repeat count specifies how many times to repeat the following command or field-description character.  If a command is not preceded by a repeat count, a 1 is assumed.  Repeat counts may range anywhere from 1 to 255.

The commands which may be included in pictures give you control over the screen and cursor.  Some of the commands allow a repeat count to be specified.  In the following descriptions, "n" refers to the optional repeat count.  If no repeat count is used, n=1.
!lm+5

/ -- Prints n carriage returns.

X -- Prints n spaces.

> -- Clear to from the cursor to the end of line.
     If the next picture character is also ">",
     clear from the cursor to the end of screen.

V -- Performs VTAB n, where n must be from 1 to 24.

H -- Performs HTAB n.  [As implemented now, this is
     probably not compatible with your printer or
     80-column cards.]
!lm-5

Literals are defined in strings using the apostrophe.  Any text you want to print from inside the picture may be included between apostrophes.  If you want to include an apostrophe inside a literal, put two apostrophes in a row.  If you put a repeat count before the literal, it will be printed n times.

Now here are some examples using repeat counts, commands, and literals.

!lm+5
&DP:PRINT $ "VH>>"     (moves the cursor to the top left
                        corner, and clears the screen.)

]&DP:PRINT $ "'SEPARATE'/'LINES'"
SEPARATE
LINES
]&DP:PRINT $ "4V10H3'BANG! '"
starting at line 4 column 10 prints:
          BANG! BANG! BANG!
!lm-5

There are two kind of field descriptions:  one for telling DP18 how to print numbers and the other for telling how to print strings.  Since string descriptors are easier, let's start with them.

A string field descriptor tells DP18 how to print the value of an Applesoft string.  There are three different characters used, which tell DP18 whether to left-justify, right-justify, or center the value of the string within the field.  Since we are building a "picture", the width of the field is shown by using multiples of the controlling character.  The three different controlling characters are:
!lm+5

A -- Print the string left justified in the field.

R -- Print the string right justified in the field.

C -- Print the string centered in the field.
!lm-5

The data to be printed comes from the list of data items which follows the picture.  Here are some examples using string descriptors:

!lm+5
]A$="ABC"
]P$="AAAAAAA'-'RRRRRRR'-'CCCCCCC'-'"
]&DP:PRINT $ P$,A$,A$,A$
ABC    -    ABC-  ABC  -
aaaaaaa.rrrrrrr.ccccccc.

]PRINT $"RRRRR X 5A 'HI' 6C 'BYE'","AB","ABC","XY"
   AB ABC  HI  XY  BYE
rrrrrxaaaaa..cccccc...
!lm-5

If you mix the A, C, and R control letters in one string field descriptor, the controlling letter will be the last one in the field.  If you want to have two fields adjacent to each other, you can separate the descriptors with a space.  The space will not become part of the printed output.  If a string value is too long to fit in a field, the field will be filled with asterisks instead of the actual data.  When you see asterisks where you expected data, the data was too long.

!lm+5
]&DP:PRINT$"AAA AAA AAA","AN","EGG","ROLLS"
AN EGG***
!lm-5

Numeric field descriptors are made up of the characters listed below.  The number to be printed is taken from the expression list.  The expression corresponding to a numeric field descriptor MUST be a DP18 expression.  If it is not a DP18 numeric expression, an error will result.  If the number is too large for the field, asterisks will be printed.  The number is rounded to the number of decimal places you specify in the descriptor before printing.  Trailing zeroes after the decimal point are printed if necessary to fill up the field.
!lm+5

+ -- Reserves a place for the sign of the number.
     the sign will be printed in this position
     even if the number is positive.  The sign
     may be placed anywhere within or at either
     end of the number.

- -- Also reserves a place for the sign, but the
     sign will only be printed if it is negative.
     If the number is positive, the fill character
     is printed instead.

!lm-5
(If neither + nor - is present in the field descriptor, the sign is printed only if the number is negative.  It is printed just to the left of the first significant digit of the number.  If you used zero or star fill, this looks ridiculous; therefore be sure to specify the sign position when you use zero or star fill.)
!lm+5

# -- Reserves a place for a digit, and selects
     space fill.  Unused digit positions to the
     left of the most significant digit will be
     filled with spaces.

* -- Reserves a place for a digit, and selects
     star fill.  Unused digit positions to the
     left of the most significant digit will be
     filled with stars.

Z -- Reserves a place for a digit, and selects
     zero fill.  Unused digit positions to the
     left of the most significant digit will be
     filled with zeroes.

. -- Reserves a position for the decimal point.
     The number will be lined up with the decimal
     point.  If no decimal point is present in
     the picture, none is printed.  Don't try
     to put more than one decimal point in one
     descriptor.

, -- Puts a comma in the number.  If the comma
     would precede all the non-blank characters
     printed in the field, the comma will not be
     printed.
!lm-5

If a mixture of #, *, and Z characters are used in field descriptor, the field will be controlled by the last one.

!lm+5
]PRINT$ "'THE ANSWER IS '###,###.##",53156.6378
THE ANSWER IS  53,156.64

]PRINT$ "####.##+/####.##-/####.##+/####.##-",
12,12.3,-12.34,-12.345
  12.00+
  12.30
  12.34-
  12.35-

]PRINT$ "5Z.3Z",125.65
00125.650
!lm-5

The listing of the DP18 PRINT code follows.  There are references to five subroutines printed in previous issues of AAL in lines 1220-1260.  The subroutines INPUT.NUM and INPUT.STR which are also referenced will not be printed until next month.  Ah, anticipation...!

When the &DP processor encounters a PRINT token, it jumps to DP.PRINT at line 1690.  I like simple code, so you can see for yourself that DP.PRINT is only three lines long.  All the work is done by PRINT.END (lines 2100-2420) and the routines it calls.

PRINT.END checks for ";" and "," separators between PRINT groups, and branches to the processors for each of the three types of PRINT groups.  Lines 2110-2130 check whether we are at the end of the PRINT statement.  If so, AS.CROUT prints a carriage return and we leave.  If not at the end, a semicolon takes us down to line 2400.  There we again check for the end, because a semicolon on the end of the PRINT statement means to omit the final carriage return.  A comma takes us to line 2380 where we force-print a carriage return (DP18's kind of tabbing, remember).

Lines 2180-2210 check for the three possible types of PRINT groups:  "$" means print with a picture, "#" means print with a w.d format, and anything else means unformatted printing.  The #w.d type is handled right here in lines 2230-2360.

Lines 2230-2250, with the help of some code in the Applesoft ROMs, read the next characters from the PRINT statement, calculate whatever expression they represent, and save the result for the field width "w".  Lines 2260-2300 do the same for "d".  Line 2310 evaluates the DP18 expression for the data value to be printed.  Lines 2320-2350 call on the FORMAT.PRINT subroutine discussed some months ago in AAL.  After printing, we go back to the top of PRINT.END to allow another PRINT group.

Unformatted printing is handled in lines 1740-2080.  Line 1750 evaluates the DP18 expression to be printed.  Lines 1760-1800 decide whether to use normal or exponential format, depending on position of the decimal point.  The exponential format is handled by QUICK.PRINT and the normal format by FOUT, both printed in an earlier installment.  We call FOUT with a format of 40 characters wide and 19 places after the decimal point.  Then we print only the significant digits of the resulting string.  All leading blanks and trailing zeroes are omitted.  If the last character is a trailing decimal point, it too is omitted.

Printing with a picture starts at line 2440.  The picture processing code is also used by DP18's INPUT$ statement, and a simple flag is used to tell who called.  PRINT sets the INPUT.FLAG = 1, INPUT sets it = 0.  INPUT$ and PRINT$ join at line 2470.

The first step in picture processing is to make a working copy of the picture in DP18's PICTURE.BUF.  Lines 2490-2510 evaluate the string expression which is the picture.  Lines 2520-2650 copy the result into PICTURE.BUF, and place a terminating $00 at the end.  Line 2680 initializes a bunch of variables so we can begin to process a field within the picture.  (PICTURE.BUF is 256 bytes long.  If you want a good project, figure out how to avoid using PICTURE.BUF.  We could with more difficulty use the picture right where it is after AS.FRMEVL finishes.)

Lines 2700-2840 control the picture parsing.  The basic idea is to scan through the picture executing command characters as we go, converting numbers to repeat counts, and printing literals.  When a field descriptor is encountered, it is built up in WBUF to form a template for the conversion.  If any of the characters of the descriptor were preceded by a repeat count, those characters will be reduplicated the specified number of times in the WBUF template.  After the template is complete, an expression will be evaluated from the PRINT list, and converted into character form.  Then those characters will be merged into the template, and the result printed.  I got ahead of myself a little, but I wanted to give the overall view first.

PRUS.NEXT calls LOOKUP to process each character of the picture.  Lookup searches the table shown in lines 3620-4010.  Each entry in the table is three bytes long:  the first byte is the character to be matched, and the next two are the address of a subroutine for processing that character.  Actually this address is one less than the subroutine address, because it will be pushed onto the stack and branched to with an RTS instruction (see lines 3160-3190 and 3260).  The order of the entries in the table is also somewhat significant.  There are three groups of entries:  the first group includes characters which may be part of a numberic field descriptor; the second, characters for string field descriptors; and the third, command characters.  The labels L.EITHER and L.BOTH mark the edges of these three groups.

If LOOKUP matches a character, it checks to see if the character is in the third group (line 2980).  If so, we know any field descriptor which may have been building is ended, so lines 3000-3010 clear the FLD.FLAG.  If not, lines 3030-3070 start a new field unless we were already in one.

Lines 3080-3140 check if we have finished a field descriptor.  We may have, if the matched character was a command character or a field-descriptor character of the opposite type field.  So, if the mathced character was a numeric-field character, we call PRT.STR.IF.NEEDED; if it was a string-field character, we call PRT.NUM.IF.NEEDED; and if a command character, we call both of the IF.NEEDED's.  The IF.NEEDED routines check if we were building up the corresponding field descriptor.  If so, we need to get a value from the PRINT list and print it now, before continuing to process the latest picture character.

Next, LOOKUP branches to the processor for the particular character matched.  It sets up the repeat count, if any has been accumulated, in the Y-register.  If no repeat count has been accumulated, y is set to 1.  The routines are all in lines 4020-5250.

If LOOKUP does not find the picture character in the table, it may be a digit of a repeat count.  If so, lines 3280-3450 multiply the existing repeat count by ten and add in the new digit.  No check for overflow is done here, so if you write a repeat count of more than 255, it will be taken modulo 256.  If you want to check for overflow, insert the check after line 3330:

                CMP #25
                BCS RP.OVERFLOW

and put a line after line 3610:

    RP.OVERFLOW JMP AS.OVRFLW

If the character is not even a digit, it is good for nothing but separating field descriptors.  Lines 3470-3480 call the two IF.NEEDED routines, in case a field descriptor preceded the non-matching character, and then fall into PRUS.CLEAR to get ready for the next picture character.

If the picture character is Z, #, or * the code at lines 4070-4240 goes to work.  There are three different entry points here.  A "Z" enters at IP.ZERO, where the A-register is cleared and a $2C opcode is used to skip over the following two bytes of code.  You may recall that $2C is the opcode for BIT with a two-byte address.  The 6502 acts like the "LDA #' '" is an address for the BIT instruction, and in effect that "hops over" line 4110.  (This is a common coding trick in the 6502 world, and is safe except when the second of the two skipped bytes is in the range from $C0 through $C7.  In that range you run the risk of flipping some soft switches in the I/O space.)

Lines 4070-4140 store zero, blank, or asterisk in FILL.CHAR and in the template being created in WBUF.  These positions in the template will later be replaced with the actual digits of the converted number, unless they precede the most significant digit.  The "w" and "d" parameters are also incremented as appropriate, so that we can later call FOUT to create the initial image of the converted number.  Lines 4220-4230 loop on the repeat count, storing multiple copies of the fill character if you used a repeat count.  We also set the FOUND.NUM flag non-zero, so that the PRT.NUM.IF.NEEDED subroutine will realize the need to print.

The RTS on the end of all the IP... processors takes control back to the middle of PRUS.NEXT, because they are actually just extensions to LOOKUP.

Lines 4290-4310 handle both the + and - picture characters.  The character is stored in the template, and also in SIGN.CHAR1 as a flag.  We need later to know whether any + or - appeared in the template at all, so the flag will be useful then.

If a decimal point appears in the picture, we store it in the template and also note the fact by setting DECFLG non-zero.  A comma is merely stored in the template.  See lines 4340-4440 for these two.

Lines 4450-4560 build templates for string field descriptors.  The characters A, C, and R and just counted, while saving the lates one in FOUND.CHAR.  When the PRT.STR.IF.NEEDED subroutine is called later, all we will need to know is which mode to use (A, C, or R) and how wide the field is.

Lines 4570-4760 print literal strings from the picture.  The only tricky part of this is the handling of the closing apostrophe.  A single apostrophe signals the end of the literal string, while two apostrophe's in a row mean an apostrophe should be printed within the literal.

Slash or "X" in a picture are handled by lines 4770-4880.  Note the use again of the $2C to skip over two bytes of code.

Lines 4900-4960 handle the HTAB command.  This is the bare minimum handling, and I can suggest some enhancements you might like to add here.  You might want to check and be sure the value is between 1 and 40, giving an error message if out of range.  You might want to adapt it to work with your particular printer and 80-column card combinations.  Or 132-column Ultra-Term.  It's up to you.

Lines 4970-5040 process the VTAB command, and here I do check for a valid line number.  Of course, if you have an Ultra-Term set up for more than 24 lines you would want to change the limit in line 5000.

Lines 5050-5180 handle the screen clearing commands.  A single ">" character calls MON.CLREOL to clear from the cursor to the end of the current line.  If the following character in the picture is also a ">", MON.CLREOS is called instead.

PRT.NUM.IF.NEEDED (lines 5190-5330)is one of the two IF.NEEDED twins.  If FOUND.NUM is non-zero, indicating that we have been building a numeric field template, then now is the time to print a number.  Unless, of course, we are doing INPUT$ rather than PRINT$.  More on that subject next month.  PRT.STR.IF.NEEDED (lines 6320-6460) does the same for strings.

When a number needs to be printed, lines 5340-5420 get it ready for conversion.  Line 5390 evaluates the next expression from the PRINT list, and it all falls into PRT.NUM.1 at line 5440.  INPUT$ has an entry at this same point.

Lines 5450-5490 make room for the sign character if the expression value is negative and a sign reservation character was used in the template.  Then W and D are correct for calling FOUT in lines 5500-5530.  The remainder of the PRINT.NUM subroutine copies characters from the FOUT.BUF string into the template, and then prints the fleshed-out template.  Sounds easier than it really is....

Lines 5540-5690 control the scan through the template in WBUF.  Commas in the template are handled right there: if any previous digits have been displayed, or if the fill character is "0" or "*", the comma is left in the template.  If no digits have been stored yet and the fill character is blank, the comma is blanked out.  It would look kind of silly hanging out in front of a number.

Lines 5700-5720 process a + or - character from the template.  The actual code for PRUS.SGN at lines 6110-6310 does the work.  If the template character is "+", it gets changed to "-" if the sign of the numeric value is negative.  If the template character is "-", it gets changed to blank if the numeric value is positive.

If the template character is a digit place-holder, the next character from FOUT.BUF is examined.  If the FOUT.BUF character is a digit, it is stored into the template.  If not a digit, it might be a decimal point, a minus sign, or a leading blank.  A leading blank gets changed to whatever the fill character is for the current template and stored in the template.  A minus sign will be stored if there was no sign-position character in the template.  A decimal point will be in the same position in both template and FOUT.BUF, so nothing needs to be done with it.

Since a sign-position character could come at the end of the template, lines 6000-6020 check for that condition.

Finally, lines 6030-6100 print out the composite string from WBUF.

String fields are printed by PRINT.STR, starting at line 6470.  Lines 6470-6550 evaluate a string expression from the PRINT list, and set up a pointer to the resulting string value.  The entry PRINT.STR.1 is shared with INPUT$.  Lines 6570-6620 determine how much longer the field is than the string value.  If it is too short, lines 6630-6700 fill the field with stars for an overflow indication.

If the string will fit, lines 6710-6750 store the number of left-over spaces in the field.  If we are left-justifying, these will all come at the end; if right-justifying, at the beginning; if centering, half on each end.  Lines 6760-6800 branch according to which type of string field we have (A, C, or R).  Lines 6810-6840 print leading spaces for type-R fields.

Lines 6850-6910 divide the number of extra spaces in half, so half can be printed before the string and half after.  If there were an odd number of extra spaces, the extra extra space will be printed after the string.  For example, a four-character string in a nine-character field would be preceded by two blanks and followed by three.

That about winds up the discussion of the DP18 PRINT support.  You can add or subtract features from this base, to create the exact configuration you need.

I should give credit to Bobby Deen for the original coding of the PRINT statement routines published this month, and the INPUT stuff next month.  I revised them considerably since he wrote them two years ago, but you can still see his marks.  Bobby is still pulling in a 4.0 average (highest possible) at Texas A & M, and programming for pay at the same time.
